package server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Map;
import java.util.StringTokenizer;

import lib.Commands;
import lib.Config;
import lib.Universe;
import lib.player.Player;

/** Class that handles one connection to the client and all commands. */
public class TCPServerUnit extends Thread {
	public Socket socket;
	public String userName = "";
	public String ip;
	public long ping;

	public int gameNum = -1; // says in which game the user is (JOIN gameNum)
	private boolean run = true;
	private boolean premission = false;
	private BufferedReader in;
	private PrintWriter out;
	private Commands c;
	private Universe universe=null;
	private int team;

	public TCPServerUnit(Socket socket) {
		this.socket = socket;
		try {
			socket.setKeepAlive(true);
		} catch (SocketException e) {
			e.printStackTrace();
		}
		ip = socket.getInetAddress().toString().substring(1);
	}

	public void run() {
		try {
			out = new PrintWriter(socket.getOutputStream(), true);
			in = new BufferedReader(new InputStreamReader(socket
					.getInputStream()));			
			String line;
			while (run) {
				line = in.readLine();
				if (line != null && !line.toUpperCase().equals("NULL"))
					parseCommands(line);
				else {
					Unitstop();
				}
			}
		} catch (SocketException e) {
		} catch (IOException e) {
			TCPServer.Error(e);
		} finally {
			try {
				System.out.println("Unit " + userName + " " + ip + " stopped.");
				TCPServer.Units.remove(this);
				list();
				in.close();
				out.close();
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Looks for commands in the input and executes the appropriate command.
	 * 
	 * @param s
	 */
	private boolean parseCommands(String s) {
		StringTokenizer st = new StringTokenizer(s, " ");
		Deque<String> args = new ArrayDeque<String>();
		for (int i = 0; st.hasMoreTokens(); i++) {
			args.push(st.nextToken());
		}

		if (args.isEmpty()) {
			invalidArgumentAnswer();
			return false;
		}

		try {
			c = Commands.valueOf(args.pollLast().toUpperCase());
			if (c == null)
				return false;

			if (userName.equals("") && c != Commands.LOGIN) {
				send(Commands.LOGIN, true);
				return false;
			}
			switch (c) {
			case STOP:
				if (premission)
					TCPServer.stop(this);
				else
					send(
							Commands.PRINT,
							" You have not the power(s) to shut this server down! Please Login as Administrator.",
							true);
				break;

			case LEAVE: // attention, LEAVE should not stop the
				// connection,
				// but solely send a signal, that the user has left
				// the chat room!
				if(args.peekLast().equals("GAME")){
					this.universe.removePlayer(userName);
					universe=null;
					break;
				}
				TCPServer.broadcast(Commands.LEAVE, userName);
				Unitstop();
				break;
			case LOGIN:
				login(args);
				list();
				break;
			case LIST:
				list();
				break;
			case HELP:
				send(Commands.PRINT,
						" Choose one out of the following commands:", true);
				for (Commands cmd : Commands.values()) {
					send(Commands.PRINT, " " + cmd.toString(), true);
				}
				break;
			case SEND:
				if (!args.isEmpty())
					TCPServer.broadcast(Commands.SEND, userName
							+ getMessage(args));
				else
					invalidArgumentAnswer();
				break;
			case JOIN:
				joinGame(args);
				break;
			case WHISPER:
				whisper(Commands.WHISPER, args.pollLast(), getMessage(args));
				break;
			case KICK:
				kick(args);
				break;
			case PING:
				ping(args);
				break;
			case MOVE:
				if (gameNum != -1 && universe != null) {
					move(args);
				} else
					send(Commands.PRINT, "LOGIN and JOIN first.", true);

				break;
			case SCORE:
				if (gameNum != -1 && universe.scores != null) {
					if (args.size() == 0) {
						send(Commands.SCORE,
								"" + universe.scores.get(userName), true);
					} else {
						String arg = args.poll();
						if (universe != null)
							if (arg.toLowerCase().equals("global")) {
								send(Commands.SCORE, HighScore
										.getSortetScores(), true);
							} else if (arg.toLowerCase().equals("local")) { // all
								// scores
								// of
								// a
								// universes
								for (Player key : universe.pl.list) {
									send(Commands.SCORE, key.name + " : "
											+ universe.scores.get(key), true);
								}
							} else if (universe.pl.getPlayer(arg) != null) { // score
								// for
								// a
								// userName...
								send(Commands.SCORE, ""
										+ universe.scores.get(arg), true);
							}
					}
				} else
					send(Commands.PRINT, "LOGIN and JOIN first.", true);
				break;
			default:
				return false;
			}

			return true;
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
			send(Commands.PRINT,
					"Command not Supportet send HELP for more informatins.",
					true);
			return false;
		}

	}

	private void list() {
		TCPServer.broadcast(Commands.LIST, "CLEAR");
		for (TCPServerUnit u : TCPServer.Units) {
			TCPServer.broadcast(Commands.LIST, u.userName + "     IP: " + u.ip
					+ "\t PING: " + u.ping + "ms");
		}		
	}

	public void sendMap(lib.map.Map map,boolean progress) {
		send(Commands.SETMAP,"SRC "+map.parser,true);
		for (int col = 0; col < Config.CollumnCount; col++) {
			for (int row = 0; row < Config.RowCount; row++) {
				send(Commands.SETMAP, progress+" "+map.getField(col, row).NR
						+ " " + col + " " + row, true);
			}
		}
	}

	/** Removes a name from the playersList of the current universe. */
	private void removeFromUniverse() {
		if (universe != null && gameNum != -1)
			universe.removePlayer(userName);
	}

	/**
	 * Move left, right, up, down. Should give the vectors(left,right...) to
	 * BlackHole which returns the next positions in the field under influence
	 * of the gravitational field.
	 * 
	 * @param args
	 */
	private void move(Deque<String> args) {
		// client number first, then direction
		Player pl = universe.pl.getPlayer(Integer.parseInt(args.pollLast()));
		String arg = args.pollLast();
		if (arg != null) {
			int direction = Integer.parseInt(arg);
			switch (direction) {
			case 0: // left
				if (universe.pl.move(pl, (int)pl.xPos - 1, (int)pl.yPos, universe))
					pl.forceHandler.vectorUpdate(-1, 0);
				break;
			case 1: // right
				if (universe.pl.move(pl, (int)pl.xPos + 1, (int)pl.yPos, universe))
					pl.forceHandler.vectorUpdate(+1, 0);
				;
				break;
			case 2: // up
				if (universe.pl.move(pl, (int)pl.xPos, (int)pl.yPos - 1, universe))
					pl.forceHandler.vectorUpdate(0, -1);
				break;
			case 3: // down
				if (universe.pl.move(pl, (int)pl.xPos, (int)pl.yPos + 1, universe))
					pl.forceHandler.vectorUpdate(0, +1);
				break;
			case 4:
				pl.ITEM.respwan(universe.map, pl, universe, universe.pl, 5);
				break;
			}
		}
	}

	/**
	 * Sends a ping to all connected clients.
	 * 
	 * @param args
	 */
	private void ping(Deque<String> args) {
		if (args.isEmpty())
			send(Commands.PRINT, "Yout Ping: " + String.valueOf(ping) + "ms",
					true);
		else {
			this.ping = System.currentTimeMillis()
					- Long.parseLong(args.pollLast());
		}
	}

	private void joinGame(Deque<String> args) {
		if (args.size() == 2) {
			if(universe!=null)return;
			String arg = args.getLast().toLowerCase();
			if (!arg.equals("new")) {
				gameNum = Integer.parseInt(args.pollLast());
				team = Integer.parseInt(args.pollLast());
				removeFromUniverse();
				universe = GamesList.gamesList.get(gameNum);
				if (GamesList.gamesList.containsKey(gameNum)) {
					System.out.println("Universename: " + universe.getName()
							+ " userName:" + userName);
					System.out.println("adding Player... " + userName);
					universe.addPlayer(userName, team, this);
					System.out.println("added Player... " + userName);
					send(Commands.PRINT,
							userName + " joined " + universe.getName()
									+ " (game id:" + gameNum + ")", true);
					Player _player = universe.pl.getPlayer(userName);
					whisper(Commands.JOIN, userName, "LOCAL "
							+ _player.toString());
					for (Player p : universe.pl.list) {
						String name = p.name;
						if (!name.equals(userName)) {
							whisper(Commands.JOIN, userName, p.toString());
						}
					}					
					universe.broadcast(Commands.JOIN, _player.toString());
					whisper(Commands.START, userName, " ");
				}
			} else {
				if (args.size() == 2) {
					args.pollLast();// JOIN NEW, adds a new game to the
					// GamesList.gamesList
					String mapName=args.pollLast();
					int size = GamesList.gamesList.size() + 1;
					// System.out.println(this);
					GamesList.gamesList.put(size, new Universe(size, mapName));
					send(Commands.PRINT, userName + " added new game.", true);
				}
			}
		} else {
			for (Map.Entry<Integer, Universe> universes : GamesList.gamesList
					.entrySet()) {
				send(Commands.UNIVERSE, universes.getKey() + " "
						+ universes.getValue().toString(), true);
				send(Commands.UNIVERSE, "STATUS " + universes.getValue().map.name, true);
				String[] status = universes.getValue().pl.score();
				for (String s : status) {
					send(Commands.UNIVERSE, "STATUS " + universes.getKey()
							+ " " + s, true);
				}
			}
		}
	}

	private boolean login(Deque<String> args) {
		if (!args.isEmpty()) {
			String Name = args.pollLast();
			for (TCPServerUnit u : TCPServer.Units) {
				if (u.userName.equals(Name)) {
					send(Commands.PRINT, "Login failed, name allready in use",
							true);
					return false;
				}
			}
			userName = Name;
			if (!args.isEmpty()) {
				if (TCPServer.Password(args.pollLast())) {
					premission = true;
					send(Commands.PRINT, "Logged in as Admin", true);
				}
			}
			TCPServer.broadcast(Commands.PRINT, "User " + userName
					+ " logged in.");
			return true;

		} else
			invalidArgumentAnswer();
		return false;

	}

	private boolean kick(Deque<String> args) {
		if (premission) {
			if (args.size() == 2) {
				if (args.getLast().toUpperCase().equals("IP")) {
					for (TCPServerUnit u : TCPServer.Units) {
						if (u.ip.equals(args.getLast())) {
							TCPServer.broadcast(Commands.KICK, u.userName);
							u.Unitstop();
							return true;
						}
					}
				} else if (args.pollLast().toUpperCase().equals("NAME")) {
					for (TCPServerUnit u : TCPServer.Units) {
						if (u.userName.equals(args.getLast())) {
							TCPServer.broadcast(Commands.KICK, u.userName);
							u.Unitstop();
							return true;

						}
					}
				}

				send(Commands.PRINT, "Unknown Host/User.", true);
			}
		} else {
			send(Commands.PRINT, "You have not the premission to kick Users.",
					true);
		}
		return false;

	}

	private boolean whisper(Commands cmd, String name, String message) {
		for (TCPServerUnit u : TCPServer.Units) {
			if (u.userName.equals(name)) {
				u.send(cmd, message, true);
				return true;
			}
		}
		send(Commands.PRINT, "Unknown User: " + name, true);
		return false;
	}

	private String getMessage(Deque<String> args) {
		String message = " ";

		while (!args.isEmpty()) {
			message += args.pollLast() + " ";
		}
		return message;
	}

	void invalidArgumentAnswer() {
		send(Commands.PRINT, "Invalid argument.", true);
	}

	/** Disconnects the connection to the client. */
	public void Unitstop() {
		removeFromUniverse();
		run = false;
		try {
			socket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void send(Commands cmd, String s, boolean log) {
		out.println(cmd + " " + s);
		if (log)
			TCPServer.log(userName, cmd + " " + s);
	}

	public void send(Commands cmd, boolean log) {
		out.println(cmd);
		if (log)
			TCPServer.log(userName, cmd.toString());
	}

}
